;==================================
;头部协议区
;==================================
SECTION header vstart=0
    program_length dd program_end
    ; 程序入口
    code_entry dw start
               dd section.code.start
    realloc_tbl_len dw (header_end-segment_realloc_table)/4
    ; 程序段重定位表
    segment_realloc_table:
    code_segment dd section.code.start
    data_segment dd section.data.start
    stack_segment dd section.stack.start
    header_end:
;===================================
SECTION code align=16 vstart=0
new_init_0x70:
    ; 中断后去 CMOS RAM 中取时分秒信息
    ; BCB 编码转换成数字
    ; 然后展示出来即可
    push ax
    push bx
    push cx
    push dx
    push es

    .wait0:
        mov al,0x0a ; 访问寄存器A
        or al,0x80  ; 阻断 NMI
        out 0x70, al
        in al,0x71 ; 读取
        test al,0x80  ; 测试最高位的UIP字段，Update In Progress，1--是处于更新周期，0--则表示正常
        jnz .wait0 ; 不为0则继续等待

    xor al,al  ; 偏移地址：0x00 —— 秒
    or al,0x80
    out 0x70,al
    in al,0x71
    push ax

    mov al,0x02 ; 偏移地址：0x02 —— 分
    or al,0x80
    out 0x70,al
    in al,0x71
    push ax

    mov al,0x04 ; 偏移地址：0x04 —— 时
    or al,0x80
    out 0x70,al
    in al,0x71
    push ax

    mov al,0x0c ; 处理未决中断
    out 0x70,al
    in al,0x71

    mov ax,0xb800
    mov es,ax

    pop ax
    call bcd_to_ascii
    mov bx,12*80*2+36*2
    
    mov [es:bx],ah
    mov byte [es:bx+1],0x07
    mov [es:bx+2],al
    mov byte [es:bx+3],0x07
    mov al, ':'
    mov [es:bx+4],al
    not byte [es:bx+5]
    
    pop ax
    call bcd_to_ascii
    
    mov [es:bx+6],ah
    mov byte [es:bx+7],0x07
    mov [es:bx+8],al
    mov byte [es:bx+9],0x07
    mov al,':'
    mov [es:bx+10],al
    not byte [es:bx+11]

    pop ax
    call bcd_to_ascii

    mov [es:bx+12],ah
    mov byte [es:bx+13],0x07
    mov [es:bx+14],al
    mov byte [es:bx+15],0x07

    mov al,0x20 ; 中断结束命令
    out 0xa0,al
    out 0x20,al

    pop es
    pop dx
    pop cx
    pop bx
    pop ax

    iret ; 从中断返回
bcd_to_ascii:
    mov ah,al
    and al,0x0f ;取个位数字
    add al,0x30 ;加0x30为ascii

    shr ah,4
    and ah,0x0f ; 十位数字
    add ah,0x30  

    ret
    
start:
    ; 初始化栈段
    mov ax, [stack_segment]
    mov ss, ax
    mov sp, stack_end
    ; 初始化数据段
    mov ax, [data_segment]
    mov ds, ax
    ; 提示信息
    mov bx, init_msg
    call put_string
    mov bx, inst_msg
    call put_string

    ; 注册中断处理函数
    mov al,0x70 ; 0x70 是 8259 从片 int 0 引脚默认的中断号
    mov bl, 4
    mul bl     ; 乘 4 后即为在 IVT 中的偏移地址
    mov bx, ax 

    cli ; 防止在设置过程中发生了中断
    push es
    mov ax,0x0000
    mov es,ax
    mov word [es:bx],new_init_0x70 ; es 用于指示 IVT 的段地址，是0x0000，中断向量表位于内存最底端
    mov word [es:bx+2],cs
    pop es

    ; 初始化还没完，要去设置RTC的中断类型
    mov al,0x0b  ; 访问寄存器B
    or al,0x80  ; 阻断 NMI
    out 0x70, al   ; 0x70 是索引端口，0x71 是数据端口，用于对 CMOS RAM 进行访问
    mov al,0x12  ; 设置更新结束中断、设置小时格式为 24 小时制
    out 0x71, al

    mov al,0x0c ; 读一下寄存器C，用于复位未决的中断状态，顺道打开 NMI
    out 0x70, al
    in al, 0x71

    ; 接下来处理 8259 的 IMR 中断屏蔽寄存器
    in al,0xa1 ; 从片端口
    and al,0xfe ; 原本是0的还是0，只把最低位置0,0代表允许中断
    out 0xa1,al
    sti

    ; 提示信息
    mov bx, done_msg
    call put_string

    mov bx, tips_msg
    call put_string

    ; 显示 @
    mov cx, 0xb800
    mov ds,cx
    mov byte [12*80*2+33*2], '@' ;定位到第12行，第33列的位置

.idle:
    hlt  ; 此时可以进入低功耗模式了，等中断程序执行
    not byte [12*80*2+33*2+1] ;每次翻转一下@符号的样式
    jmp .idle

; put_string 入参 bx=字符串首地址
put_string:
    mov cl,[bx]
    or cl,cl ; 如果为0则退出循环
    jz .exit
    call put_char
    inc bx
    jmp put_string
    .exit: ret
; ===============
; put_char cl=将要输出的字符
; ===============
put_char:
    push ax
    push bx
    push cx
    push dx
    push ds
    push es
    ; 因为要控制光标移动，所以先取当前光标位置
    mov dx,0x3d4 ; 显卡的索引寄存器端口号为 0x3d4
    ; 索引值是有一个列表的，每个索引值都对应着一个显卡的内部寄存器
    ; 先通过索引值告诉显卡要操作的寄存器
    mov al,0x0e
    out dx,al
    ; 再通过数据端口进行读写
    mov dx,0x3d5
    in al,dx
    mov ah,al

    mov dx,0x3d4
    mov al,0x0f
    out dx,al
    mov dx,0x3d5
    in al,dx

    mov bx,ax ; 用 BX 来记录光标位置

    ; 开始输出文本
    cmp cl,0x0d  ; 回车符？
    jnz put_0a  ; 不是则进入下一个判断，看看是不是换行符
    mov ax,bx   ; 当前游标除以80，商即为行数（从0开始），再乘以80，即为当前行首游标
    mov bl,80
    div bl     ; AL--商，代表行数，AH--余数，舍弃
    mul bl     ; AX中为乘积
    mov bx,ax  ; 结果保存到 BX 中
    jmp set_cursor

put_0a:
    cmp cl,0x0a  ; 换行符？
    jnz put_normal
    add bx,80
    jmp scroll_screen

put_normal:
    mov ax,0xb800
    mov es,ax
    shl bx,1 ; 左移一位就是乘以2。BX是坐标位置，可代表是第多少个字符，乘2后就是要在显卡中写入的位置
    mov ch,0x07
    mov [es:bx],cx

    shr bx,1 ; 刚刚 bx 左移了一位，现在右移回来
    inc bx

scroll_screen:
    cmp bx,2000  ; 80x25 模式下最多就显示2000个字符。光标还要占一个字符，文字最多1999个
    jl set_cursor ; jmp if less than
    ; 滚屏本质是将2~25行全部向上挪一行，最后一行用黑底白字的空格填充
    mov ax,0xb800
    mov ds,ax
    mov es,ax
    mov si,2*80
    mov di,0
    mov cx,80*24
    cld
    rep movsw

    mov bx,1920*2 ; 定位到最后一行
    mov cx,80 ; 循环80次
cls:
    mov word[es:bx],0x0720
    add bx,2
    loop cls

    mov bx,1920

set_cursor:
    mov dx,0x3d4
    mov al,0x0e
    out dx,al
    mov dx,0x3d5
    mov al,bh
    out dx,al

    mov dx,0x3d4
    mov al,0x0f
    out dx,al
    mov dx,0x3d5
    mov al,bl
    out dx,al
    
    pop es
    pop ds
    pop dx
    pop cx
    pop bx
    pop ax

    ret
;===================================
SECTION data align=16 vstart=0
    init_msg       db 'Starting...',0x0d,0x0a,0
                   
    inst_msg       db 'Installing a new interrupt 70H...',0x0d,0x0a,0
    
    done_msg       db 'Done.',0x0d,0x0a,0

    tips_msg       db 'Clock is now working.',0x0d,0x0a,0
;===================================
SECTION stack align=16 vstart=0
    resb 256
stack_end:
;===================================
SECTION trailer align=16
program_end: